#include "Object.hpp"

#include "Model.hpp"
#include "Context.hpp"

Object::Object(Object * parent /* = 0 */,
               const glm::mat4 & modelMatrix /* = glm::mat4(1.0f) */,
               Model * model /* = 0 */,
               const std::string & name
              )
  : m_parent(0), m_modelMatrix(modelMatrix), m_model(model), m_name(name)
{
  if (parent) {
    parent->attachChild(this);
  }
  
  if (m_model) {
    m_model->attachParent(this);
  }
  
  m_ul_model = g_context.m_progs[Context::Normal]->getUniformLocation("MatrixModel");
}

Object::~Object()
{
  if (m_model) {
    size_t count = m_model->detachParent(this);
    if (count == 0) {
      delete m_model;
    }
  }
  
  std::list<Object*>::iterator it;
  for (it = m_children.begin(); it != m_children.end(); ++it) {
    delete (*it);
  }
}

void Object::draw(const glm::mat4 & modelMatrix)
{
  glm::mat4 newModelMatrix = modelMatrix * m_modelMatrix;
  //DEBUG("Drawing %s", m_name.c_str());
  if (m_model) {
    g_context.useProgram(Context::Normal);
    glUniformMatrix4fv(m_ul_model, 1, false,
                       &newModelMatrix[0][0]);
    m_model->draw();
  }
  
  std::list<Object*>::iterator it;
  for (it = m_children.begin(); it != m_children.end(); ++it) {
    (*it)->draw(newModelMatrix);
  }
  
}

void Object::update()
{
  std::list<Object*>::iterator it;
  for (it = m_children.begin(); it != m_children.end(); ++it) {
    (*it)->update();
  }
}

glm::mat4 Object::getWorldModelMatrix() const
{
  mat4 mat(1.0f);
  
  for (const Object * obj = this; obj != 0; obj = obj->m_parent) {
    mat = obj->m_modelMatrix * mat;
  }

  return mat; 
}

void Object::setModelMatrix(const glm::mat4 & modelMatrix)
{
  m_modelMatrix = modelMatrix;
}

void Object::transform(const glm::mat4 & transform)
{
  m_modelMatrix = transform * m_modelMatrix; 
}

void Object::detachChild(Object * obj)
{
   if (obj->m_parent != this)
     return;
   m_children.remove(obj);
   obj->m_parent = 0;
   
}

void Object::attachChild(Object * child)
{
  
  if (child->m_parent) {
    
    // recompute model matrix
    Object * lca = lowestCommonAncestor(child);
    
    mat4 oldModelMatrix(1.0f);
    for (Object * obj = child; obj != lca; obj = obj->m_parent) {
      oldModelMatrix = obj->m_modelMatrix * oldModelMatrix;
    }
    
    mat4 parentModelMatrix(1.0f);
    for (Object * obj = this; obj != lca; obj = obj->m_parent) {
      parentModelMatrix = obj->m_modelMatrix * parentModelMatrix;
    }
    
    mat4 newModelMatrix = glm::inverse(parentModelMatrix) * oldModelMatrix;
    
    child->m_parent->detachChild(child);
    child->m_modelMatrix = newModelMatrix;
    
  }
  
  m_children.push_back(child);
  child->m_parent = this;
}

Object * Object::lowestCommonAncestor(Object * other)
{
  std::vector<Object*> path;
  
  for (Object * obj = this; obj != 0; obj = obj->m_parent) {
    path.push_back(obj);
  }
  
  for (Object * obj = other; obj != 0; obj = obj->m_parent) {
    std::vector<Object*>::iterator it;
    for (it = path.begin(); it != path.end(); ++it) {
      if (obj == (*it))
        return obj;
    }
  }
  return 0;
}

Iterator Object::begin()
{
  return Iterator(this); 
}

Iterator Object::end()
{
  return Iterator(this->m_parent);
}

Iterator& Iterator::operator++()
{
  if (!m_obj->m_children.empty()) {
    // descend into next level
    m_itrs.push(m_itr);
    m_parent = m_obj;
    m_itr = m_parent->m_children.begin();
    m_obj = *m_itr;
    return *this;
  }
  
  // move across same level
  while (!m_itrs.empty()) {
    
    ++m_itr;
    if (m_itr != m_parent->m_children.end()) {
      m_obj = *m_itr;
      return *this;
    }
    
    //ascend
    m_obj = m_parent;
    m_parent = m_obj->m_parent;
    
    m_itr = m_itrs.top();
    m_itrs.pop();
  }
  
  m_obj = 0;
  return *this;
}


